/*
 * This file or a portion of this file is licensed under the terms of
 * the Globus Toolkit Public License, found in file ../GTPL, or at
 * http://www.globus.org/toolkit/download/license.html. This notice must
 * appear in redistributions of this file, with or without modification.
 *
 * Redistributions of this Software, with or without modification, must
 * reproduce the GTPL in: (1) the Software, or (2) the Documentation or
 * some other similar material which is provided with the Software (if
 * any).
 *
 * Copyright 1999-2004 University of Chicago and The University of
 * Southern California. All rights reserved.
 */
package org.griphyn.vdl.classes;

import edu.isi.pegasus.common.util.Currently;
import org.griphyn.vdl.classes.*;
import org.griphyn.vdl.util.*;
import java.util.*;
import java.io.*;

/**
 * This class implements the container to carry any number of
 * <code>Transformation</code> and <code>Derivation</code> instances.
 * In addition, it captures some attributes from the root element of
 * the XML document. 
 *
 * @author Jens-S. Vöckler
 * @author Yong Zhao
 * @version $Revision: 2079 $
 *
 * @see Definition
 * @see Transformation
 * @see Derivation
 */
public class Definitions extends VDL implements Serializable
{
  /**
   * The "official" namespace URI of the VDLx schema.
   */
  public static final String SCHEMA_NAMESPACE =
    "http://www.griphyn.org/chimera/VDL";

  /**
   * The "not-so-official" location URL of the VDLx schema definition.
   */
  public static final String SCHEMA_LOCATION =
    "http://www.griphyn.org/chimera/vdl-1.24.xsd";

  // attributes of "definitions" element
  /**
   * Capture the global namespace given to all child elements that
   * do not set their own namespace definition.
   */
  private String m_vdlns;

  /**
   * Capture the version of the XML document.
   */
  private String m_version;

  /**
   * children are either {@link Transformation}s or {@link Derivation}s,
   * both of which are {@link Derivation}s.
   */
  private ArrayList m_definitionList;

  /**
   * ctor. It is strongly suggested that you set the namespace and
   * version of the object before adding any other {@link Definition}
   * objects.
   */
  public Definitions()
  {
    this.m_definitionList = new ArrayList();
    this.m_vdlns = null;
    this.m_version = null;
  }

  /**
   * ctor: Create a new container, using the given namespace. It is
   * highly recommended that you set the version number before you add
   * any {@link Definition} instance.
   *
   * @param vdlns is the namespace to use for elements w/o namespace.
   */
  public Definitions( String vdlns )
  {
    this.m_definitionList = new ArrayList();
    this.m_vdlns = vdlns;
    this.m_version = null;
  }

  /**
   * ctor: Create a new container, using a namespace and version.
   * 
   * @param vdlns is the namespace to propagate to children w/o namespace.
   * @param version is a version of the XML document used to transport
   * the data.
   */
  public Definitions( String vdlns, String version )
  {
    this.m_definitionList = new ArrayList();
    this.m_vdlns = vdlns;
    this.m_version = version;
  }

  /**
   * updating old Use.linkage with new Use.linkage. This table uses
   * the old/stored linkage in the top row, and the new/found linkage
   * in the first column. (-) denotes no action to be taken, and #
   * an illegal combination.<p>
   * <pre>
   *       | -1  | NONE| IN  | OUT | IO
   *   ----+-----+-----+-----+-----+----
   *    -1 | (-) | (-) | (-) | (-) | (-)
   *   NONE| NONE| (-) |  #  |  #  |  # 
   *     IN| IN  |  #  | (-) | IO  | (-)
   *    OUT| OUT |  #  | IO  | (-) | (-)
   *     IO| IO  |  #  | IO  | IO  | (-)
   *   ----+-----+-----+-----+-----+----
   * </pre>
   * The table uses -1 for no action to do, and -2 for an illegal state.
   */
  private int m_state[][] = { { -1, -1, -1, -1, -1 }, // newlink == -1
			      {  0, -1, -2, -2, -2 }, // newlink == NONE
			      {  1, -2, -1,  3, -1 }, // newlink == IN
			      {  2, -2,  3, -1, -1 }, // newlink == OUT
			      {  3, -2,  3,  3, -1 } };
  /**
   * Checks the linkage of a transformation between a declared, previously
   * used and currently used variable of the same name.
   *
   * @param use is a table of previously used variables and their linkage
   * @param u is the variable at the "cursor position".
   * @param tr is the transformation to be checked.
   */
  private void checkLinkage( Map use, Use u, Transformation tr )
  {
    if ( use.containsKey( u.getName() ) ) {
      // key exists, check/modify linkage
      int linkage = ((Integer) use.get( u.getName() )).intValue();
      int newlink = u.getLink();
      int result = this.m_state[newlink+1][linkage+1];
      if ( result == -2 ) {
	// illegal combination of linkages, usually NONE w/ I,O,IO
	throw new IncompatibleLinkageException( "Transformation " + tr.shortID() +
						"uses variable " + u.getName() +
						" with incompatibles linkages" );
      } else if ( result > -1 ) {
	// store new result
	use.put( u.getName(), new Integer(result) );
      }
    } else {
      // key does not exist, add 
      use.put( u.getName(), new Integer(u.getLink()) );
    }
  }

  /**
   * Clean-up definition and perform abstract type checks before
   * submitting them into the document.
   *
   * @exception IllegalArgumentException will be thrown if the
   * <code>Definition</code> is neither a <code>Derivation</code> nor a
   * <code>Transformation</code>. This should not happen.
   * @exception UndeclaredVariableException will be thrown, if
   * a <code>Transformation</code> uses a bound variable via <code>Use</code>,
   * but fails to declare the formal argument with <code>Declare</code>.
   * @exception IncompatibleLinkageException will be thrown, if
   * the declared linkage of a formal argument is incompatible with the
   * usage of such a bound variable within a <code>Transformation</code>.
   * @exception IllegalTransformationException will be thrown, if
   * the <code>Transformation</code> has simultaneously <code>Call</code>
   * and <code>Argument</code> items. This exception is bound to vanish
   * with the next major re-design.
   *
   * @see Transformation
   * @see Derivation
   * @see Use
   * @see Declare
   */
  protected void sanitizeDefinition( Definition d )
    throws IllegalArgumentException, 
	   IncompatibleLinkageException,
	   UndeclaredVariableException,
	   IllegalTransformationException
  {
    String newline = System.getProperty( "line.separator", "\r\n" );

    // update definition with namespace and version, if necessary
    // Note: results may still be null
    if ( d.getNamespace() == null && this.m_vdlns != null )
      d.setNamespace(this.m_vdlns);
    if ( d.getVersion() == null && this.m_version != null )
      d.setVersion(this.m_version);
    
    switch ( d.getType() ) {
    case Definition.TRANSFORMATION:
      Transformation tr = (Transformation) d;
      HashMap use = new HashMap();

      // a TR must not be simultaneously simple and compound
      if ( tr.getArgumentCount() > 0 && tr.getCallCount() > 0 ) 
	throw new IllegalTransformationException( "TR " + tr.identify() +
						  " is simultaneously simple and compound" +
						  newline + tr.toXML("\t",null) );
      
      //
      // collect all unique bindings of class Use 
      //
      if ( tr.isSimple() ) {
	// collect from Argument list
	for ( Iterator e=tr.iterateArgument(); e.hasNext(); ) {
	  for ( Iterator f=((Argument) e.next()).iterateLeaf();	f.hasNext(); ) {
	    Leaf l = (Leaf) f.next();
	    if ( l instanceof Use ) checkLinkage( use, (Use) l, tr );
	  }
	}
      } else {
	// collect from Call list, this is slightly more complex...
	// only 3..4 nested for loops, why do you worry...
	for ( Iterator e=tr.iterateCall(); e.hasNext(); ) {
	  for ( Iterator f=((Call) e.next()).iteratePass(); f.hasNext(); ) {
	    Value v = (Value) ((Pass) f.next()).getValue();
	    switch ( v.getContainerType() ) {
	    case Value.SCALAR:
	      for ( Iterator g=((Scalar) v).iterateLeaf(); g.hasNext(); ) {
		Leaf l = (Leaf) g.next();
		if ( l instanceof Use ) checkLinkage( use, (Use) l, tr );
	      }
	      break;
	    case Value.LIST:
	      for ( Iterator h=((List) v).iterateScalar(); h.hasNext(); ) {
		for ( Iterator g=((Scalar) h.next()).iterateLeaf(); g.hasNext(); ) {
		  Leaf l = (Leaf) g.next();
		  if ( l instanceof Use ) checkLinkage( use, (Use) l, tr );
		}
	      }
	      break;
	    }
	  }
	}
      }
      // collect from Profile list
      for ( Iterator e=tr.iterateProfile(); e.hasNext(); ) {
	for ( Iterator f=((Profile) e.next()).iterateLeaf(); f.hasNext(); ) {
	  Leaf l = (Leaf) f.next();
	  if ( l instanceof Use ) checkLinkage( use, (Use) l, tr );
	}
      }

      // check usages against all declared and temporary variables. Also
      // check linkage. Note that the declared variables must have a
      // linkage. It is permissable to declare variables, but not use
      // them.
      for ( Iterator i=use.keySet().iterator(); i.hasNext(); ) {
	String name = (String) i.next();

	// check that the used variable is declared
	Declare dec = (Declare) tr.getDeclare(name);
	Local local = (Local) tr.getLocal(name);
	if ( dec == null && local == null )
	  throw new UndeclaredVariableException( "variable " + name +
						 " is used, but not declared" +
						 newline + tr.toXML("\t",null) );

	// match up linkages. Note that a use linkage of -1 means
	// that we don't have any information on the used linkage.
	int dLinkage = ( dec==null ? local.getLink() : dec.getLink() );
	int uLinkage = ((Integer) use.get(name)).intValue();
	if ( uLinkage > -1 ) {
	  if ( dLinkage == LFN.NONE   && uLinkage != LFN.NONE ||
	       dLinkage == LFN.INPUT  && uLinkage != LFN.INPUT ||
	       dLinkage == LFN.OUTPUT && uLinkage != LFN.OUTPUT ||
	       dLinkage == LFN.INOUT  && uLinkage == LFN.NONE )
	    throw new IncompatibleLinkageException( "variable " + name +
						    " uses incompatible linkages" +
						    newline + tr.toXML("\t",null) );
	}
      }
      break;
    case Definition.DERIVATION:
      Derivation dv = (Derivation) d;

      if ( dv.getUsesspace() == null ) {
	if ( this.m_vdlns != null ) {
	  // either default uses namespace to vdlns
	  dv.setUsesspace(this.m_vdlns);
	} else if ( d.getNamespace() != null ) {
	  // or default uses namespace to derivation namespace
	  dv.setUsesspace( d.getNamespace() );
	}
      }

      // nothing really to check for derivations
      // note: Do *not* check here, if a DV has a matching TR, because
      // in the future, TR will be stored in distributed database(s).
      break;

    default:
      // this must not happen
      throw new IllegalArgumentException("Definition " + d.identify() +
					 " is neither TR nor DV");
    }
  }
  
  /**
   * Accessor: Appends a {@link Definition} to the container. The
   * namespace and version information will be, in case they are
   * missing, updated from the definitions namespace and version
   * respectively.
   * 
   * @param d is the {@link Transformation} or {@link Derivation}
   * to append to the internal container.
   * @throws IndexOutOfBoundsException if the definition does not fit into
   * the container.
   */
  public void addDefinition( Definition d )
    throws IndexOutOfBoundsException
  { 
    this.sanitizeDefinition(d);
    this.m_definitionList.add(d); 
  }
  
  /**
   * Accessor: Inserts a {@link Definition} at a particular place. The
   * namespace and version information will be, in case they are
   * missing, updated from the definitions namespace and version
   * respectively.<p>
   *
   * Each component in this vector with an index greater or equal to the
   * specified index is shifted upward to have an index one greater than
   * the value it had previously.
   *
   * @param index is the position to insert a {@link Definition}
   * @param d is the {@link Transformation} or {@link Derivation}
   * to append to the internal container.
   * @throws IndexOutOfBoundsException if the definition does not fit into
   * the container.
   */
  public void addDefinition( int index, Definition d )
    throws IndexOutOfBoundsException
  { 
    this.sanitizeDefinition(d);
    this.m_definitionList.add(index, d); 
  }

  /**
   * Accessor: Search the database for the existence of a Definition with
   * the same primary keys and type as the parameter.
   * @param d is the Definition to search for 
   * @return the position of the selfsame Definition, or -1 if not found.
   */
  public int positionOfDefinition( Definition d ) 
  {
    int n=0;
    for ( Iterator i=this.m_definitionList.iterator(); i.hasNext(); ++n ) {
      if ( d.equals(i.next()) ) return n;
    }
    return -1;
  }

  /**
   * Accessor: Provides an iterator for the list of {@link Transformation}
   * and {@link Derivation}.
   * 
   * @return the iterator to traverse the container of {@link Definition}s.
   * @see java.util.Enumeration
   * @deprecated Use the new Collection based interfaces
   */
  public Enumeration enumerateDefinition()
  { 
    return Collections.enumeration(this.m_definitionList);
  }
  
  /**
   * Obtains a vector of all definition instances that share the same
   * instance type. Please note that the definitions below may change
   * after the vector is obtained.
   *
   * @return a vector with all {@link Transformation} or {@link Derivation}
   * objects. The vector may have zero size, if no such instances exist. 
   */
  public java.util.List getDefinitionOfAKind( int type )
  {
    ArrayList result = new ArrayList();
    for ( Iterator i=this.m_definitionList.iterator(); i.hasNext(); ) {
      Definition d = (Definition) i.next();
      if ( d.getType() == type ) {
	result.add(d);
      }
    }

    return result;
  }

  /**
   * Accessor: Obtains a <code>Definition</code> at a particular place
   * in this container.
   *
   * @param index is the place to look up the element.
   * @return the <code>Definition</code> at the specified place.
   * @throws IndexOutOfBoundsException if the referenced position does
   * not exist.
   * @see Definition
   */
  public Definition getDefinition(int index)
    throws IndexOutOfBoundsException
  {
    //-- check bound for index
    if ((index < 0) || (index >= this.m_definitionList.size())) 
      throw new IndexOutOfBoundsException();

    return (Definition) this.m_definitionList.get(index);
  }

  /**
   * Accessor: Obtains all {@link Definition}s available.
   * This array is a copy to avoid write-through modifications.
   *
   * @return an array containing either a {@link Transformation}
   * or {@link Derivation} at each position.
   * @deprecated Use the new Collection based interfaces
   */
  public Definition[] getDefinition()
  {
    int size = this.m_definitionList.size();
    Definition[] mDefinition = new Definition[size];
    System.arraycopy( this.m_definitionList.toArray(new Definition[0]), 0,
		      mDefinition, 0, size );
    return mDefinition;
  }

  /**
   * Accessor: Counts the number of {@link Transformation} and
   * {@link Derivation} definitions.
   * @return item count.
   */
  public int getDefinitionCount()
  {
    return this.m_definitionList.size(); 
  }

  /**
   * Accessor: Obtains all {@link Definition}s available.
   * This list is read-only. 
   *
   * @return an array containing either a {@link Transformation}
   * or {@link Derivation} at each position.
   */
  public java.util.List getDefinitionList()
  {
    return Collections.unmodifiableList(this.m_definitionList);
  }

  /**
   * Accessor: Obtains the document namespace.
   * @return the namespace of the document, or null, if not used.
   * @see #setVdlns(java.lang.String)
   */
  public String getVdlns()
  { 
    return this.m_vdlns; 
  }

  /** Accessor: Obtains the document version number.
   * 
   * @return the version number from the document header, or null,
   * if unset. Since the version number is a required attribute,
   * it should never return null, only an empty string.
   */
  public String getVersion()
  { 
    return this.m_version; 
  }

  /**
   * Accessor: Provides an iterator for the list of {@link Transformation}
   * and {@link Derivation} references.
   * 
   * @return a list iterator to traverse the container of {@link Definition}s.
   * @see java.util.ListIterator
   */
  public Iterator iterateDefinition()
  { 
    return this.m_definitionList.iterator(); 
  }

  /**
   * Accessor: Provides an iterator for the list of {@link Transformation}
   * and {@link Derivation} references.
   * 
   * @return a list iterator to traverse the container of {@link Definition}s.
   * @see java.util.ListIterator
   */
  public ListIterator listIterateDefinition()
  { 
    return this.m_definitionList.listIterator(); 
  }

  /**
   * Accessor: Provides an iterator for the list of {@link Transformation}
   * and {@link Derivation} references.
   *
   * @param start is the starting point of the iteration.
   * @return a list iterator to traverse the container of {@link Definition}s.
   * @see java.util.ListIterator
   */
  public ListIterator listIterateDefinition( int start )
  { 
    return this.m_definitionList.listIterator(start); 
  }

  /**
   * Accessor: Removes all definitions we know about.
   * @see Definition
   */
  public void removeAllDefinition()
  { 
    this.m_definitionList.clear(); 
  }

  /**
   * Accessor: Removes a definition. Each component in this vector with
   * an index greater or equal to the specified index is shifted
   * downward to have an index one smaller than the value it had
   * previously. The size of this vector is decreased by 1.
   *
   * @param index is the position to remove the argument fragment from.
   * @return the removed Definition.
   * @exception ArrayIndexOutOfBoundsException if the index was invalid.
   * @see Definition
   */
  public Definition removeDefinition(int index)
  {
    return (Definition) this.m_definitionList.remove(index);
  }

  /**
   * Accessor: Removes a definition named by its reference. Removes the
   * first occurrence of the specified element in this Vector.
   *
   * @param d is a definition instance that originated from this list.
   * @return true, if the first occurance of the element was deleted,
   * false, if there was nothing found to be removed.
   * @see Definition
   */
  public boolean removeDefinition( Definition d )
  {
    return this.m_definitionList.remove(d);
  }

  /**
   * Accessor: Sets the component at the specified index of this vector
   * to be the specified object. The previous component at that position
   * is discarded. The index must be a value greater than or equal to 0
   * and less than the current size of the vector.
   *
   * @param index is the postion at which to replace a {@link Definition}.
   * @param d is either a {@link Transformation} or 
   * {@link Derivation} to use for replacement.
   * @throws  IndexOutOfBoundsException if the index was invalid.
   */
  public Definition setDefinition(int index, Definition d)
    throws IndexOutOfBoundsException
  {
    //-- check bounds for index
    if ((index < 0) || (index >= this.m_definitionList.size())) {
      throw new IndexOutOfBoundsException();
    }

    this.sanitizeDefinition(d);
    return (Definition) this.m_definitionList.set(index, d);
  }

  /**
   * Accessor: Replace all {@link Definition}s with a new list.
   *
   * @param definitionArray is an array of possibly mixed 
   * {@link Transformation} and {@link Derivation} elements.
   * @deprecated Use the new Collection based interfaces
   */
  public void setDefinition(Definition[] definitionArray)
  {
    this.m_definitionList.clear();
    this.m_definitionList.addAll( Arrays.asList(definitionArray) );
  }

  /**
   * Accessor: Replace all {@link Definition}s with a new list.
   *
   * @param definitions is an collection of possibly mixed 
   * {@link Transformation} and {@link Derivation} elements.
   */
  public void setDefinition(Collection definitions)
  {
    this.m_definitionList.clear();
    this.m_definitionList.addAll(definitions);
  }

  /**
   * Accessor: Sets the document default namespace.
   * 
   * @param vdlns is the new namespace to use. Note that the change will
   * <b>not</b> be propagated to contained elememts.
   * @see #getVdlns()
   */
  public void setVdlns(String vdlns)
  { 
    this.m_vdlns = vdlns; 
  }

  /**
   * Accessor: Replaces the version number of the document.
   *
   * @param version is the new version number.
   * @see #getVersion()
   */
  public void setVersion( String version )
  { 
    this.m_version = version; 
  }

  /**
   * Dumps the content of the given element into a string. This function 
   * traverses all sibling classes as necessary and converts the 
   * data into textual output. 
   *
   * @param stream is a stream opened and ready for writing. This can also
   * be a string stream for efficient output.
   * @exception IOException if something fishy happens to the stream.
   */
  public void toString( Writer stream )
    throws IOException
  {
    for ( Iterator i=this.m_definitionList.iterator(); i.hasNext(); ) {
      ((Definition) i.next()).toString(stream);
    }
  }

  /**
   * Writes the header of the XML output. The output contains the special
   * strings to start an XML document, some comments, and the root element.
   * The latter points to the XML schema via XML Instances. 
   *
   * @param stream is a stream opened and ready for writing. This can also
   * be a string stream for efficient output.
   * @param indent is a <code>String</code> of spaces used for pretty
   * printing. The initial amount of spaces should be an empty string.
   * The parameter is used internally for the recursive traversal.
   * @param namespace is the XML schema namespace prefix. If neither
   * empty nor null, each element will be prefixed with this prefix,
   * and the root element will map the XML namespace. 
   * @exception IOException if something fishy happens to the stream.
   */ 
  public void writeXMLHeader( Writer stream, String indent, String namespace )
    throws IOException
  {
    String newline = System.getProperty( "line.separator", "\r\n" );

    if ( indent != null && indent.length() > 0 ) stream.write( indent );
    stream.write( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" );
    stream.write( newline );

    if ( indent != null && indent.length() > 0 ) stream.write( indent );
    stream.write( "<!-- generated: " );
    stream.write( Currently.iso8601(false) );
    stream.write( " -->" );
    stream.write( newline );

    if ( indent != null && indent.length() > 0 ) stream.write( indent );
    stream.write( "<!-- generated by: " );
    stream.write( System.getProperties().getProperty("user.name", "unknown") );
    stream.write( " [" );
    stream.write( System.getProperties().getProperty("user.region","??") );
    stream.write( "] -->" );
    stream.write( newline );

    // start root element
    if ( indent != null && indent.length() > 0 ) stream.write( indent );
    stream.write( '<' );
    if ( namespace != null && namespace.length() > 0 ) {
      stream.write( namespace );
      stream.write( ':' );
    }
    stream.write( "definitions xmlns" );
    if ( namespace != null && namespace.length() > 0 ) {
      stream.write( ':' );
      stream.write( namespace );
    }
    stream.write( "=\"");
    stream.write( SCHEMA_NAMESPACE );
    stream.write( "\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"");
    stream.write( SCHEMA_NAMESPACE );
    stream.write( ' ' );
    stream.write( SCHEMA_LOCATION );
    stream.write( '"' );

    writeAttribute( stream, " vdlns=\"", this.m_vdlns );
    writeAttribute( stream, " version=\"", this.m_version );

    stream.write( '>' );
    if ( indent != null ) stream.write( newline );
  }

  /**
   * Dump the state of the current element as XML output. This function
   * traverses all sibling classes as necessary, and converts the data
   * into pretty-printed XML output. The stream interface should be able
   * to handle large output efficiently, if you use a buffered writer.
   *
   * @param stream is a stream opened and ready for writing. This can also
   * be a string stream for efficient output.
   * @param indent is a <code>String</code> of spaces used for pretty
   * printing. The initial amount of spaces should be an empty string.
   * The parameter is used internally for the recursive traversal.
   * @param namespace is the XML schema namespace prefix. If neither
   * empty nor null, each element will be prefixed with this prefix,
   * and the root element will map the XML namespace. 
   * @exception IOException if something fishy happens to the stream.
   */
  public void toXML( Writer stream, String indent, String namespace )
    throws IOException
  {
    // write prefix
    writeXMLHeader( stream, indent, namespace );

    // optionally write content
    if ( this.m_definitionList.size() > 0 ) {
      String newindent = indent==null ? null : indent + "  ";
      for ( Iterator i=this.m_definitionList.iterator(); i.hasNext(); ) {
	((Definition) i.next()).toXML( stream, newindent, namespace );
      }
    }

    // finish document
    if ( indent != null && indent.length() > 0 ) stream.write( indent );
    stream.write( "</" );
    if ( namespace != null && namespace.length() > 0 ) {
      stream.write( namespace );
      stream.write( ':' );
    }
    stream.write( "definitions>" );
    stream.write( System.getProperty( "line.separator", "\r\n" ) );
    stream.flush(); // this is the only time we flush
  }
}
